/**
 * Implementation of methods from Decision Tree.
 */

#include <boost/python.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <vector>
#include <faif/learning/Classifier.hpp>
#include <faif/learning/DecisionTree.hpp>
#include "zprDecisionTree.hpp"
#include "pyListToVector.hpp"
#include "typedefs.hpp"
#include "vectorWrapper_py.hpp"


using namespace std;
using namespace boost::python;
using namespace faif;

/*
 * Public methods of ZPRDecisionTree
 */

/**
 * \brief ZPRDecisionTree constructor
 *  @return void
 */
ZPRDecisionTree::ZPRDecisionTree(): pointerToDecisionTree_(new DTC), domains_(), attrDomain_(), examples_() {
    newDomains_=false;
    haveAttrDomain_=false;
}

/**
 * \brief Read domain to domains list - to apply domains invoke applyTree()
 * Method extract from python object list, and then we transform it into std::vector
 * so we can send it to our DTC and operate on given now, and earlier doamins
 * @param Domain name
 * @param Domain
 * @see applyTree()
 * @return void
 */
void ZPRDecisionTree::zprPassDomain(boost::python::object domain_name, boost::python::object domain) {
	boost::python::list pyList = boost::python::extract<boost::python::list>(domain);
	string name = extract<string>(domain_name);
    std::vector<string> domainVector = PyContainersConverters::pyListToVector<std::string>(pyList);
	std::cout << "Passing Domain into tree... Number of domains: " << domainVector.size()<<"\n";
	string* sPtr = &domainVector[0];
	try {
        domains_.push_back( faif::createDomain<std::string>(name, sPtr,sPtr + domainVector.size() ));
        newDomains_ = true;
	}
    catch(...){
        std::cout<<"Faied to pass Domains...";
    }
}

/**
 * \brief Read category and add to list of domain attributes
 * Method extract from python object list, and then we transform it into std::vector
 * so we can send it to our DTC and operate on given now, and earlier domain attributes
 * @param Domain Attributes
 * @return void
 */
void ZPRDecisionTree::zprPassAttributeDomain(boost::python::object attributeDomain) {
	boost::python::list pyList = extract<boost::python::list>(attributeDomain);
    std::vector<string> attributeVector = PyContainersConverters::pyListToVector<std::string>(pyList);
	string* sPtr = &attributeVector[0];
	try {
        attrDomain_ = faif::createDomain("empty Domain",  sPtr, sPtr+ attributeVector.size() );
        haveAttrDomain_ = true;
	}
	catch(...){
        std::cout<<"Failde to pass Attributr Domain...";
	}
}

/**
 * \brief Read category with given earlier classifier name and add to list of domain attributes
 * @param Domain Attributes
 * @param Name of Category
 * @return void
 */
void ZPRDecisionTree::zprPassAttributeDomainWithName(boost::python::object category_name, boost::python::object attributeDomain) {
	boost::python::list pyList = extract<boost::python::list>(attributeDomain);
	string name = extract<string>(category_name);
    std::vector<string> attributeVector = PyContainersConverters::pyListToVector<std::string>(pyList);
	string* sPtr = &attributeVector[0];
	try {
        attrDomain_ = faif::createDomain(name,  sPtr, sPtr+ attributeVector.size() );
        haveAttrDomain_ = true;
	}
	catch(...){
        std::cout<<"Failde to pass Attributr Domain...";
	}
}

/**
 * \brief Pass into tree that have Domain, and AttribureDomain, Examples to Train
 * @param trainExaples is a object that should have exmaples to learn
 * @param trainCatrgory is a object that should have category of trained example
 * @see zprPassDomain()
 * @see zprPassAttributeDomain()
 * @return void
 */
void ZPRDecisionTree::zprPassTrainExamples(boost::python::object trainExamples, boost::python::object trainCategory){
    boost::python::list pyList = extract<boost::python::list>(trainExamples);
    string category = extract<string>(trainCategory);
    std::vector<string> examplesVector = PyContainersConverters::pyListToVector<std::string>(pyList);
    try {
        if(haveAttrDomain_ && newDomains_)
            examples_.push_back(faif::ml::createExample(&(*examplesVector.begin()), &(*examplesVector.end()), category, *pointerToDecisionTree_));
    }
    catch(...) {
        if(!haveAttrDomain_ || !newDomains_)
            std::cout<<"Can't learn without AttrDomain and Domain. No knowledge...";
    }
}

/**
 * \brief Pass into tree that have Domain, and AttribureDomain, Examples Test
 * @param testExaples is a object that should have exmaples test
 * @return void
 */
void ZPRDecisionTree::zprPassTestExample(boost::python::object testExamples) {
    boost::python::list pyList = extract<boost::python::list>(testExamples);
    std::vector<string> examplesVector = PyContainersConverters::pyListToVector<std::string>(pyList);
    try {
        if(haveAttrDomain_ && newDomains_)
            tests_ = faif::ml::createExample(&(*examplesVector.begin()), &(*examplesVector.end()), *pointerToDecisionTree_);
    }
    catch(...) {
        if(!haveAttrDomain_ || !newDomains_)
            std::cout<<"Can't learn without AttrDomain and Domain. No knowledge...";
    }
}



/**
 * \brief Method that makes tree from parameters given earlier by invoking zprPass functions
 * @see zprPassDomain()
 * @see zprPassAttributeDomain()
 * @see zprPassAttributeDomainWithName()
 * @see zprPassTainExamples()
 * @return void
 */
void ZPRDecisionTree::applyTree() {
	try {
		if(haveAttrDomain_ && newDomains_) {
			pointerToDecisionTree_ = new DTC(domains_, attrDomain_);
			domains_.clear();
			newDomains_ = false;
		}
	}
	catch(...){
	    if(!haveAttrDomain_ || !newDomains_)
            std::cout<<"Can't make tree without parameters given earlier (Domains and Attributes)...";
	}

}

/**
 * \brief Reset the DecisionTree
 * @return void
 */
void ZPRDecisionTree::zprReset() {
    pointerToDecisionTree_->reset();
}

/**
 * Train DecisionTree wiht gived example_, domain_, category_
 * @return void
 */
void ZPRDecisionTree::zprTrain() {
    cout<<"Training... \n";
    pointerToDecisionTree_->train(examples_);
}

/**
 * \brief Gets category of Tree
 * @return main category (attrDomain) in boost::python::object
 */
object ZPRDecisionTree::zprGetCategoryDomainFromDTC() {
	boost::python::list pyList;
	try {
		AttrDomain attribute = pointerToDecisionTree_->getCategoryDomain();
		cout <<"Size: "<<attribute.getSize()<<"\n";
		for(AttrDomain::iterator it = attribute.begin();it!=attribute.end();++it) {
			pyList.append(it->get());
			std::cout << " val = " << *it << std::endl;
			std::cout << " val->get() = " << it->get() << std::endl;
			std::cout << " val->getDomain() = " << it->getDomain() << std::endl;

		}
	}
	catch(...){
		cout<<"Geting Category Domain failed...";
	}
	return object(pyList);
}

/**
 * \brief Gets category of Tree
 * @return main category (attrDomain) in boost::python::object
 */
object ZPRDecisionTree::zprGetAttrDomainsFromDTC() {
	Domains domains = pointerToDecisionTree_->getAttrDomains();
	boost::python::list pyDomainsList;
	for(Domains::iterator it = domains.begin();it!=domains.end();++it) {
		boost::python::list enumList;
		for(faif::DomainEnumerate<ValueNominal<std::string> >::iterator enumIt = it->begin();enumIt != it->end();++enumIt) {
			enumList.append(enumIt->get());
		}
		pyDomainsList.append(enumList);
	}
	return object(pyDomainsList);
}

/**
 * \brief Gets category of given Example Test
 * @param Example Test
 * @return main category (attrDomain) in boost::python::object
 */
object ZPRDecisionTree::zprGetCategoryFromDTC(object o) {
    boost::python::list pyList = extract<boost::python::list>(o);
    boost::python::list pyList2;
    std::vector<string> zprList = PyContainersConverters::pyListToVector<std::string>(pyList);
    ZprExampleTest tests = faif::ml::createExample(&(*zprList.begin()), &(*zprList.end()), *pointerToDecisionTree_);
    DTC::AttrIdd idd = pointerToDecisionTree_->getCategory(tests);
	try {
	    pyList2.append("Category of given example Test: ");
        pyList2.append(idd->get());
    }
	catch(...){
		cout<<"Geting Category failed...";
	}
	return object(pyList2);
}

/**
 * \brief Gets one of AttrDomain
 * @param AttrDomain, string
 * @return category
 */
object ZPRDecisionTree::zprGetCategoryIddFromDTC(object o) {
	try{
		ZprAttrValue value = extract<string>(o);
		ZprAttrIdd idd = pointerToDecisionTree_->getCategoryIdd(value);
        boost::python::list pyList;
        pyList.append(idd->get());
        return object(pyList);
	}
	catch(...)
	{
		std::cout << "None value\n";
		return object();
	}
}

/**
 * \brief Gets Beliefs
 * @param Example Test
 * @return Beliefs
 */
object ZPRDecisionTree::zprGetCategoriesFromDTC(object o) {
    boost::python::list pyList = extract<boost::python::list>(o);
    boost::python::list pyList2;
    std::vector<string> zprList = PyContainersConverters::pyListToVector<std::string>(pyList);
    ZprExampleTest tests = faif::ml::createExample(&(*zprList.begin()), &(*zprList.end()), *pointerToDecisionTree_);
    ZprBeliefs idd = pointerToDecisionTree_->getCategories(tests);
	try {
	    for(ZprBeliefs::iterator it = idd.begin();it!=idd.end();++it)
			pyList2.append(*it);
    }
	catch(...){
		cout<<"Geting Categories failed...";
	}
	return object(pyList2);
}

void ZPRDecisionTree::zprSetParamToDTC(object o) {
    /// TODO. Must take struct
    return;
}

/*
 * private methods of ZPRDecisionTree
 */

/**
 * \brief Private Constructor used by public methods to create DTC with given earlier Domain, AttrDomain
 */
ZPRDecisionTree::ZPRDecisionTree(const Domains& attr_domains, const AttrDomain& category_domain):
    pointerToDecisionTree_(new DTC(attr_domains,category_domain)) {}
